#coding=utf-8
'''
二维码扫描代码
'''
import logging
import time
import cv2
import numpy as np
import pyzbar.pyzbar as pyzbar
import datetime
import requests
import json
from xpinyin import Pinyin
from pyDes import des, PAD_PKCS5, ECB   #  des模式  填充方式  ECB加密方式

# 自己的包
import config
import gps


'''
最终版，扫描二维码（乘客或者司机），上传到服务器验证，返回验证数据，显示

注：在扫描用户码时，树莓派只是单纯的将字符串发送到服务器，服务器负责把字符串解析成json报文或者是python对象。
    在扫描司机二维码时，是在树莓派端解析成json报文（因为乘客二维码的格式是我写的）
'''
def scan_code():

    config.DRIVER_QRCODE_TIME = datetime.datetime.now()       # 初始化司机第一次扫码的时间

    while(config.QRCODE_STOP == 0):                                              # 循环不停扫码
        
        ret0,img0 = config.cap.read()                         # 读走缓冲区的图片，弃掉不用，保证实时性
        ret1,img1 = config.cap.read()                         # 读走缓冲区的图片，弃掉不用，保证实时性(实践证明，读两次是最佳选择)
        ret, img = config.cap.read()                          # 读入图片
        img_ROI_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转灰度
        barcodes = pyzbar.decode(img_ROI_gray)                # 解码，返回识别到的多个二维码对象的列表

        back_string = 'None'                                  # 从服务器返回的字符串

        if len(barcodes) > 0:                               # 如果检测到二维码
            time0 = datetime.datetime.now()
            config.qrcode_count = config.qrcode_count + 1   # 扫码次数统计

            data_string = barcodes[0].data.decode("utf-8")  # 每次只选择一个二维码，并且解码,data_string是解码字符串

            (x,y,w,h) = barcodes[0].rect                    # 获取二维码坐标,坐标格式：[横坐标，纵坐标，长度，宽度]  单位：像素
            cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)  # 画出二维码的轮廓框
            cv2.imshow('image', img)                        # 显示轮廓
            print('[scan_code]扫码器扫到的数据：  ', data_string)

            if 'driver' in data_string:                # 二维码类型分类，如果是司机二维码

                #time1 = datetime.datetime.now()
                desecret_string = des_decode(data_string)             # 解密司机二维码，还原最初无标记字符串
                back_string = send_driver(desecret_string)            # 调用send_driver函数，解析字符串，向服务器验证，同时返回验证信息
                cv2.putText(img, back_string , (20,100), config.font,1,(0,255,0),4)     # 打函数返回字符在图片上（可能是验证成功的字符，可能是验证失败的字符）
                cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)        # 画出二维码的轮廓框
                cv2.imshow('image', img)                                        # 显示
                #time2 = datetime.datetime.now()
                #print('[scan_code]司机二维码扫描处理耗费时间：  ', time2 - time1) 
            else:                                           # 二维码类型分类，如果是乘客二维码
                back_string = send_passager(data_string)
                cv2.putText(img, back_string , (20,100), config.font,1,(0,255,0),4)     # 打函数返回字符在图片上（可能是验证成功的字符，可能是验证失败的字符）
                cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)  # 画出二维码的轮廓框
                cv2.imshow('image', img)                                        # 显示     
            time4 = datetime.datetime.now()
            print('[scan_code]:树莓派一次扫码，解析，处理的时间  ', time4 - time0)  
            logging.debug('[scan_code]:树莓派一次扫码，解析，处理的时间  ' + str(time4 - time0) )
            time.sleep(4)                 
        else:                                               # 如果未检测到二维码
            # 实例拼音转换对象
            config.config_lock.acquire() # 加锁（此处数据是从gps得到的，所以可能存在前后不一致，需要加锁解锁）
            ret_str = ''
            if config.NOW_PLATFORM >= 0 and len(config.platform_name)>0: 
                ret_str = Pinyin().get_pinyin(config.platform_name[config.NOW_PLATFORM])
            show_string = 'status: '+ str(config.BUS_STATUS) + '  platform: ' + ret_str
            if config.GPS_AVAILABLE == 1:
                gps_show_string = 'GPS: on-line'
            else:
                gps_show_string = 'GPS: off-line'
            config.config_lock.release() # 解锁
            cv2.putText(img, gps_show_string,(10,165),config.font,0.6,(0,255,0),2)    # 打印GPS状态
            cv2.putText(img, show_string , (10,200), config.font,0.6,(255,0,0),2)     # 打印车辆状态和站点
            cv2.imshow('image', img)
            time.sleep(0.2) # 隔0.5秒，读一次图片
    config.cap.release()
    cv2.destroyAllWindows() 
    return
        

'''
function：
    将司机二维码的字符串解析成json报文，使用http协议发送json报文，接受服务器的验证信息，
    如果验证成功，返回 司机账号+ok，如果验证失败，返回字符串'None'
    如果验证成功，则可以初始化树莓派的车辆id和司机id（在config.py中）
    防止司机重复验证司机二维码

司机的二维码格式(字符串)：
    driver_id:xxxxxxxx,bus_id:xxxxxx[后面可能还有拓展，服务器端生成二维码可以写，这里可以不用]
    driver_id是司机的工号，bus_id是车牌号

司机数据发送至服务器格式（json）：
    {'driver_id': 'xxxxxxxx', 'bus_id': 'xxxxxx'}

服务器返回的验证数据格式（json报文）：
    {'driver_id': 'xxxxxxxx', 'bus_id': 'xxxxxx', 'type': 'on/off/false'}    on表示上班打卡，off表示下班打卡， false表示下班打卡，fasle表示错误的二维码
'''
def send_driver(str1):

    # 解析二维码字符串
    json_driver = {}
    attribute_list = str1.split(",")  # 切割字符串的每个属性
    if len(attribute_list) != 3:      # 如果属性数量不相符，则刷码失败
        return 'None'

    for attribute in attribute_list:  # 对每个属性，切割出其 key 和 value
        key_value_list = attribute.split(':')
        if len(key_value_list) != 2:
            return 'None'
        json_driver[ key_value_list[0] ] = key_value_list[1]  # 把每个 key 和 value 加入字典
    print('[send_driver]司机二维码解析出来的json报文数据', json_driver)
    logging.debug('[send_driver]司机二维码解析出来的json报文数据' + str(json_driver))

    # 字典内容完整性检查
    if 'driver_id' not in json_driver:    
        return 'None'
    if 'bus_id' not in json_driver:       
        return 'None'

    # 司机重复扫码检测
    if config.DRIVER_ID != config.blank and config.DRIVER_ID != json_driver['driver_id']:  # 当前非第一次司机扫码，并且是另一个司机扫码
        print('[send_driver]：其他司机打卡不生效')
        logging.debug('[send_driver]：其他司机打卡不生效')
        return 'Other invalid driver'
    else:
        time_now = datetime.datetime.now()
        if time_now - config.DRIVER_QRCODE_TIME > config.DRIVER_TIMEDELTA:    # 每次成功的司机扫码之间必须间隔 config.DRIVER_TIMEDELTA 时间
            print('[send_driver]：司机即将上下班打卡')
            #logging.debug('[send_driver]：司机即将上下班打卡')
        else:
            print('[send_driver]：司机扫码过于频繁，请稍等')
            logging.debug('[send_driver]：司机扫码过于频繁，请稍等')
            return 'Please wait!'

    # 上传至服务器验证（阻塞）,同时接受返回回来的数据
    try:
        response = requests.post(url=config.URL+config.driver_URL, json=json_driver)  
        response.raise_for_status()                                       # 如果响应状态码不是 200，就主动抛出异常
    except requests.RequestException as e:
        print(e)
        return 'network error'
    #response = requests.post(url=config.URL+config.driver_URL, json=json_driver)  
    ret_type = json.loads(response.text).get('type')

    # 回传数据处理和显示
    if ret_type == 'on':                                          # 司机上班打卡
        config.BUS_ID = json.loads(response.text).get('bus_id')   # 初始化树莓派需要绑定的信息
        config.DRIVER_ID = json.loads(response.text).get('driver_id')

        config.OLD_BUS_STATUS = config.BUS_STATUS
        config.BUS_STATUS = 0
        logging.debug('[send_driver]车辆状态 BUS_STATUS ：' + str(config.BUS_STATUS))

        print('[send_driver] 司机 上班 打卡联网验证成功')
        logging.debug('[send_driver] 司机 上班 打卡联网验证成功')
        config.DRIVER_QRCODE_TIME = datetime.datetime.now()       # 更新司机最后一次扫码的时间
        return config.DRIVER_ID + '  on work'

    elif ret_type == 'off':                                       # 司机下班打卡,需要清空树莓派信息

        '''
        # 为了防止同步错误，这里先把重置这些参数语句移动到gps的主函数中
        config.qrcode_count = 0
        config.old_BUS_ID = config.BUS_ID
        config.BUS_ID = config.blank                 
        config.old_DRIVER_ID = config.DRIVER_ID
        config.DRIVER_ID = config.blank
        config.old_passagers = {'first', 'last'} 
        config.platform_pk = []
        config.platform_name = []
        config.platform_longitude = []
        config.platform_latitude = []
        '''
        # 依次清除其他数据
        config.config_lock.acquire() # 加锁（防止出现，判断的时候是-1，执行清空函数时是非-1，导致有效数据被误清空）
        config.OLD_BUS_STATUS = config.BUS_STATUS
        config.BUS_STATUS = -1   # 先修改状态
        config.qrcode_count = 0
        config.old_BUS_ID = config.BUS_ID
        config.BUS_ID = config.blank                 
        config.DRIVER_ID = config.blank
        config.old_passagers = {'first', 'last'} 
        config.platform_pk = []
        config.platform_name = []
        config.platform_longitude = []
        config.platform_latitude = []
        config.LATITUDE = 0.0
        config.LONGITUDE = 0.0
        config.config_lock.release() # 解锁

        logging.debug('[send_driver]车辆状态 BUS_STATUS ：' + str(config.BUS_STATUS))
        config.DRIVER_QRCODE_TIME = datetime.datetime.now()       # 更新司机最后一次扫码的时间
        print('[send_driver] 司机 下班 打卡联网验证成功')
        logging.debug('[send_driver] 司机 下班 打卡联网验证成功')
        #config.old_DRIVER_ID = config.DRIVER_ID
        driver_id = json.loads(response.text).get('driver_id')
        return driver_id + ' off work'
    else:
        print('[send_driver]：错误的司机二维码')
        logging.debug('[send_driver]：错误的司机二维码')
    return 'None'

    

'''
function:
    发送乘客的二维码（json），同时验证返回回来的数据，
    服务器：如果验证成功，返回乘客账号，如果验证失败，返回字符串'None'
    司机未扫码前，乘客不得扫码

乘客二维码格式：
    字符串
    “用户id字符串,用户乘车的nonce”
发送至服务器格式（json）
    {'bus_id':'......', 'code':"...........二维码扫描到的字符串", 'platform': 1/2/3/4}
从服务器接受的格式（json）：
    {'type':'true/false', 'passager_id':'xxxx','note':'二维码已扫描/二维码非法'}

point：只有司机扫码才会把车辆状态从>=0改为 -1,而乘客，司机扫码是串行的，所以这里乘客扫码验证不用加锁

'''
def send_passager(str2):

    # if config.BUS_ID == config.blank and config.DRIVER_ID == config.blank:  # 如果树莓派没有初始化车辆身份信息，是不允许乘客扫码的
    #     return 'Uninitialized Car'
    if config.BUS_STATUS <= 0:  # 如果树莓派没有初始化车辆身份信息，是不允许乘客扫码的
        return 'Uninitialized Car'

    # 乘客重复扫码检查(使用非重复集合)
    if str2 in config.old_passagers:
        print('[send_passager]:乘客重复扫码')
        logging.debug('[send_passager]:乘客重复扫码')
        return 'Duplicate QR code'
    # 检测不合规的二维码
    list1 = str2.split(',')
    if len(list1) != 2:  
        return 'Error QR Code'

    json_passager = {}
    json_passager['platform'] = config.NOW_PLATFORM
    json_passager['bus_id'] = config.BUS_ID
    json_passager['code'] = str2
    logging.debug('[send_passager]乘客扫码准备发送的数据： ' + str(json_passager))
    try:
        response = requests.post(url=config.URL+config.passage_URL, json=json_passager)  # 上传至服务器验证（阻塞）,同时接受返回回来的数据
        response.raise_for_status()                                       # 如果响应状态码不是 200，就主动抛出异常
    except requests.RequestException as e:
        print(e)
        return 'network error'
    #response = requests.post(url=config.URL+config.passage_URL, json=json_passager)  # 上传至服务器验证（阻塞）,同时接受返回回来的数据

    ret_type = json.loads(response.text).get('type')
    if ret_type == 'true':                                        # 判断数据是否验证成功
        passager_id = json.loads(response.text).get('passager_id')
        config.old_passagers.add(str2)
        print('[send_passager]乘客扫码验证成功  passager_id:  ', passager_id)
        logging.debug('[send_passager]乘客扫码验证成功  passager_id:  '+str(passager_id) )
        return passager_id + '  OK'
    else:
        config.old_passagers.add(str2)
        note = json.loads(response.text).get('note')
        return 'Error: ' + note



'''
function：
    解密被使用des加密的司机二维码
输入：
    密码二维码字符串
输出：
    明文字符串
加密字符串格式：
“driver_id:*********”  为了适应源代码中的config.driver
'''
def des_decode(secret_str):
    hex_str = secret_str.replace('driver','')
    secret_bytes = bytes.fromhex(hex_str)
    des_obj = des(config.DES_SECRET_KEY, ECB, config.DES_SECRET_KEY, padmode=PAD_PKCS5)  # 初始化一个des对象，参数是秘钥，加密方式，偏移， 填充方式
    s = des_obj.decrypt(secret_bytes)   # 用对象的decrypt方法解密
    ret_string = s.decode('utf-8')      # 使用utf-8编码，把解密过后的字节流转化成字符串
    return ret_string



# main函数
if __name__ == '__main__':
    '''
    str3= 'driver_id:1170300402,bus_id:黑A1234'
    ret = send_driver(str3)
    print(ret)
    '''
    scan_code()